__author__ = 'benxing'
# -*- coding: utf-8 -*-

import sys
import tornado.web
import tornado.ioloop
import logging
import json
from uucode.uuCode import UUcode,str_md5,get_lfirst
from tornado.httpclient import AsyncHTTPClient,HTTPRequest
import re
import datetime
import traceback
from tornado.ioloop import PeriodicCallback
from requests.models import RequestEncodingMixin
import time

logger = logging.getLogger('info')
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('/home/log/uucode_tornado.log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)

uu = UUcode('109521', '3c8906d3666e44bb9c961e1647126dbc')
auth_key = 'wydata'

asynClient = AsyncHTTPClient()

class UUCodeMainHandler(tornado.web.RequestHandler):
    def __init__(self,application,request,**kwargs):
        super(UUCodeMainHandler,self).__init__(application=application,request=request,**kwargs)

        # 定时回调函数句柄
        self.callback_timer = None

        # 点数
        self.balance = 0

        # 需要识别的验证码
        self.files = None

        # 识别结果
        self.notify_result = []
        self.code_type = None
        self.max_times = None
        self.cur_time = 0
        self.log = []
        self.code_id = ''

    # 1.获取(刷新)服务器列表:
    # Get http://common.taskok.com:9000/Service/ServerConfig.aspx
    #
    # /// 域名:端口:类型 例如
    # /// login.abc.com:8080:101,upload.abc.com:8080:102,result.abc.com:8080:103,
    # /// 类型101表示登录服务器，102表示上传服务器，103表a示获取结果服务器，104表示备份服务器
    # （当upload连续出错的话，调用备份服务器,备份服务器包含所有方法，登录，上传，取得结果）
    #
    # 1000,service.taskok.com:9000:101,upload.taskok.com:9000:102,code.taskok.com:9000:103,bak.taskok.com:104
    # 1000,表示间隔1000毫秒，请求一次getResult
    def refresh_servers(self, url='http://common.taskok.com:9000/Service/ServerConfig.aspx'):
        try:
            header = uu.initHeader(userID='100')
            _request = HTTPRequest(url=url, headers=header)
            asynClient.fetch(request=_request, callback=self.callback_load_refresh, request_timeout=5)
        except Exception as e:
            _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + str(e.message)
            self.log.append(_msg)

    # refresh servers 函数的回调函数
    def callback_load_refresh(self, response):
        if response.code == 200 and ':101' in response.body:
            uu.time_interval_getResult = get_lfirst(response.body.split(','))
            uu.servers = {
                'login_server': get_lfirst(re.compile('(?<=,)[^,]*?(?=:101)').findall(response.body)),
                'upload_server': get_lfirst(re.compile('(?<=,)[^,]*?(?=:102)').findall(response.body)),
                'getResult_server': get_lfirst(re.compile('(?<=,)[^,]*?(?=:103)').findall(response.body)),
                'backup_server': get_lfirst(re.compile('(?<=,)[^,]*?(?=:104)').findall(response.body)),
            }
            suc = True
        else:
            self.log.append('|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + 'refresh server error [status:%s]' % response.code)
            suc = False

        if not suc:
            self.log.append('|' + __file__ + '/' + str(
                sys._getframe().f_lineno) + '|' + 'refresh servers failed')
            self.code_id = ''
            self.make_response_code(result='refresh servers failed',successful='false')
        else:
            # 调用验证码识别函数
            for file in self.files:
                content = file['body']

                try:
                    self.process_notify_code(img_content=content)
                except Exception as e:
                    _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + str(e)
                    self.log.append(_msg)
                    self.make_response_code(result=_msg, successful='false')


    # 3.查分
    # Get http://login.abc.com/Upload/GetScore.aspx?U={0}&p={1}
    # {0}为用户名 {1}为密码的str_md5值
    #
    # 返回结果：
    # 成功返回当前的用户点数
    # 失败返回错误代码
    def get_balance(self, login_server, userName, userPasswd):
        try:
            if uu.userKey:
                url = 'http://%s/Upload/GetScore.aspx' % login_server
                # param = {'U': userName, 'p': str_md5(userPasswd)}
                real_url = url + '?U=' + userName + '&p=' + str_md5(userPasswd)

                _request = HTTPRequest(url=real_url, headers=uu.getHeader_balance(userKey=uu.userKey))
                asynClient.fetch(request=_request, callback=self.callback_balance, request_timeout=5)
            else:
                self.make_response_balance(_msg='userKey is None', successful='false')
        except Exception as e:
            _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + str(e.message)
            self.log.append(_msg)
            self.make_response_balance(_msg=_msg, successful='false')

    def callback_balance(self, response):

        try:
            self.balance = response.body
            self.make_response_balance(_msg=self.balance, successful='true')
        except Exception as e:
            _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + str(e.message)
            self.log.append(_msg)
            self.make_response_balance(_msg=_msg, successful='false')

    # 4.上传图片
    # 在上传图片之前，需要请求 http://common.taskok.com:9000/Service/ServerConfig.aspx 来刷新服务器列表
    # Post http://upload.abc.com/Upload/Processing.aspx
    #
    # 数据：
    # KEY：  		UserKey.ToUpper()  登录返回的数据
    # SID:		软件id
    # SKEY:		求str_md5(UserKey.ToLower()+软件id+软件key)
    # Version:	100
    # TimeOut:	超时时间，单位为毫秒，必须大于20000
    # Type:		验证码的CodeType，详见：http://www.uuwise.com/price.html
    # IMG：   	图片文件
    # GUID   		图片文件的str_md5()
    #
    # 返回结果：
    #
    #
    # 成功返回：
    # 验证码id 			//这个时候表示无结果，需要请求getresult
    # 或者
    # 验证码id|Result 	//Result即为识别结果
    # 例如：12130123|ssdd
    #
    # 失败返回错误代码
    def process_notify_code(self, img_content):
        if uu.userKey is not None:
            try:
                url = 'http://%s/Upload/Processing.aspx' % uu.servers['upload_server']
                header = uu.initHeader(uu.userID)
                header['SID'] = uu.userID
                data = {
                    'KEY': uu.userKey.upper(),
                    'SID': uu.softID,
                    'SKey': str_md5(uu.userKey.lower() + uu.softID + uu.softKey),
                    'Version': 100,
                    'Type': self.code_type,
                    'GUID': str_md5(img_content)
                }

                img = (str(time.time()) + '.jpg', img_content)
                body, content_type = RequestEncodingMixin._encode_files(files={'IMG': img}, data=data)
                headers = {'Content-Type': content_type}

                _request = HTTPRequest(url=url, method='POST', headers=headers, body=body, request_timeout=60)
                asynClient.fetch(request=_request, callback=self.callback_notify_code)

            except Exception as e:
                _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + str(e.message)
                self.log.append(_msg)
                self.make_response_code(result=_msg, successful='false')
        else:
            self.make_response_code(result='userKey is None', successful='false')

    # 验证结果
    def callback_notify_code(self, response):
        try:
            self.cur_time += 1

            start_time = datetime.datetime.now()
            _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + 'Start identifying code ...... [%s]' % start_time
            self.log.append(_msg)

            if response.code == 200:
                if response.body is not None:
                    if '|' in response.body:
                        self.code_id, result = response.body.split('|')[0:2]

                        # 得到结果，返回
                        self.make_response_code(result=result, successful='true')
                    else:
                        self.code_id = response.body
                        self.get_result()
                else:
                    _msg = '|' + __file__ + '/' + str(
                        sys._getframe().f_lineno) + '|' + 'Upload IMG : response.text is None'
                    self.log.append(_msg)
                    self.report_error()
            else:
                _msg = '|' + __file__ + '/' + str(
                    sys._getframe().f_lineno) + '|' + 'Upload IMG : Error %d' % response.code
                self.log.append(_msg)
                self.report_error()

        except Exception as e:
            _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + str(e.message)
            self.log.append(_msg)
            self.make_response_code(result=_msg, successful='false')


    # 5. 获取识别结果

    # GET http://result.abc.com/Upload/GetResult.aspx?key={0}&ID={1}
    # {0}为UserKey, {1}为验证码id
    #
    # 返回结果:
    # 如果返回结果为-3，则代表服务器还未识别完成，间隔1000毫秒，循环请求getResult
    # 成功返回验证码识别结果，错误返回错误代码
    def get_result(self):
        try:
            _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + 'get_result[id=%s]' % (self.code_id)
            self.log.append(_msg)
            url = 'http://%s/Upload/GetResult.aspx' % uu.servers['getResult_server']
            real_url = url + '?KEY=' + uu.userKey + '&ID=' + str(self.code_id)
            _request = HTTPRequest(url=real_url, headers=uu.initHeader(uu.userID), request_timeout=1)
            asynClient.fetch(request=_request, callback=self.callback_get_result)
            self.cur_time += 1
        except Exception as e:
            _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + str(e.message)
            self.log.append(_msg)
            self.make_response_code(result=_msg, successful='false')

    def callback_get_result(self,response):
        try:
            if response.code == 200:
                result = response.body
                if result != '-3':
                    _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + 'Got Result: %s' % (result)
                    self.log.append(_msg)

                    # 得到验证结果，结束请求，返回
                    self.make_response_code(result=result,successful='true')

                else:
                    if self.cur_time < self.max_times:
                        if not self.callback_timer:
                            self.callback_timer = PeriodicCallback(callback=self.get_result, callback_time=1*1000)
                            self.callback_timer.start()
                            # self.callback_timer = self.start_timer(5, callback=self.get_result)
            else:
                _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + 'Fail to get Result: %s' % (response.body)
                self.log.append(_msg)
                self.make_response_code(result=_msg, successful='false')

        except Exception as e:
            _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + '[Exception] get_result_error : ' + str(e)
            self.log.append(_msg)
            self.make_response_code(result=_msg, successful='false')


    def make_response_balance(self, _msg, successful='true'):
        try:
            log_count = len(self.log)
            for i in range(log_count):
                log_content = self.log.pop(0)
                logger.log(logging.INFO, log_content)

            _result = {}
            _result['status'] = successful
            _result['balance'] = _msg

            self.set_header("Content-Type", "application/json;charset=utf-8")
            _write_str = json.dumps(_result)
            self.write(_write_str)
        except Exception as e:
            self.write(str(e))
        finally:
            self.finish()

    def make_response_code(self, result, successful='true'):
        try:
            if self.callback_timer:
                self.callback_timer.stop()
                self.callback_timer = None
            log_count = len(self.log)
            for i in range(log_count):
                log_content = self.log.pop(0)
                logger.log(logging.INFO, log_content)

            _result = {}
            _result['status'] = successful
            _result['id'] = self.code_id
            _result['result'] = result

            self.set_header("Content-Type", "application/json;charset=utf-8")
            _write_str = json.dumps(_result)
            self.write(_write_str)
        except Exception as e:
            self.write(str(e))
        finally:
            self.finish()


    # 6. 报错
    # GET http://result.abc.com/Upload/ReportError.aspx?KEY={0}&ID={1}&SID={2}&SKEY={3}
    # {0}为UserKey,{1}为验证码id，{2}为软件id，{3}为SKEY，值为str_md5(UserKey.ToLower()+软件id+软件key)
    #
    # 返回结果：
    # 成功返回OK， 错误返回错误代码
    def report_error(self):
        uu.refresh_servers()
        url = 'http://%s/Upload/ReportError.aspx' % uu.servers['getResult_server']
        # suc, _ = self.login_servers(userName=self.userName, userPasswd=self.userPasswd)
        if uu.have_login:
            # param = {'KEY': uu.userKey, 'ID': id, 'SID': uu.softID,
            #          'SKEY': str_md5(uu.userKey.lower() + uu.softID + uu.softKey)}
            try:
                _real_url = url + '?KEY=' + uu.userKey + '&ID=' + self.code_id + '&SID=' + uu.softID + '&SKEY=' + str_md5(uu.userKey.lower() + uu.softID + uu.softKey)
                _request = HTTPRequest(url=_real_url)
                asynClient.fetch(request=_request, callback=self.callback_report_error)
            except Exception as e:
                _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + '[Exception] up_error : ' + str(e)
                self.log.append(_msg)

    def callback_report_error(self,response):

        try:
            result = response.body
            _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + 'up_error : ' + result
            self.log.append(_msg)

            _result = {}
            _result['status'] = 'true'
            _result['id'] = self.code_id
            _result['msg'] = 'report status:' + result

            self.set_header("Content-Type", "application/json;charset=utf-8")
            _write_str = json.dumps(_result)
            self.write(_write_str)
        except Exception as e:
            _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + '[Exception] up_error : ' + str(e)
            self.log.append(_msg)
        finally:
            self.finish()

    def notify_code(self):
        self.refresh_servers()

    @tornado.web.asynchronous
    def get(self):
        try:
            uri = self.request.path
            if uri == '/balance':
                login_server = uu.servers['login_server']
                self.get_balance(login_server=login_server, userName=uu.userName, userPasswd=uu.userPasswd)
            elif uri == '/report':
                code_id = self.get_argument('id', None)
                if not code_id:
                    raise Exception('id can not be null')

                self.code_id = code_id
                self.report_error()

                self.set_header("Content-Type", "application/json;charset=utf-8")
        except Exception as e:
            self.set_header("Content-Type", "application/json;charset=utf-8")
            self.write(str(e))
            logger.log(logging.DEBUG, str(e))


    @tornado.web.asynchronous
    def post(self):
        try:
            timestamp = self.get_argument('timestamp', None)
            if timestamp is None:
                _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + 'miss paramter[timestamp]'
                self.log.append(_msg)
                raise Exception(_msg)
            timestamp = timestamp.encode('utf8')

            token = self.get_argument('token', None)
            if token is None:
                _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + 'miss paramter[token]'
                self.log.append(_msg)
                raise Exception(_msg)
            token = token.encode('utf8')

            auth_res = str(timestamp) + auth_key
            auth_res = str_md5(auth_res)

            if auth_res != token:
                _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + 'Authentic failed'
                self.log.append(_msg)
                raise Exception(_msg)

            max_times = self.get_argument('max_times', 20)
            self.max_times = int(max_times.encode('utf8'))
            code_type = self.get_argument('code_type', None)
            self.code_type = code_type.encode('utf8')

            if code_type is None:
                _msg = '|' + __file__ + '/' + str(sys._getframe().f_lineno) + '|' + 'code_type is null'
                self.log.append(_msg)
                raise Exception(_msg)

            try:
                self.files = self.request.files["file"]
                self.notify_code()
            except Exception as e:
                logger.log(logging.INFO, str(e))
                self.set_header("Content-Type", "text/plain")
                self.write(str(e))
        except Exception as e:
            self.set_header("Content-Type", "application/json;charset=utf-8")
            self.write(str(e))
            logger.log(logging.DEBUG, str(e))

def login_server():
    uu.refresh_servers()
    uu.login_servers(userName=uu.userName, userPasswd=uu.userPasswd)

pc_login = PeriodicCallback(callback=login_server, callback_time=24*60*1000)
pc_login.start()

application = tornado.web.Application([(r"/",UUCodeMainHandler),(r"",UUCodeMainHandler),(r"/balance",UUCodeMainHandler),(r"/report",UUCodeMainHandler),])

# def test_timer():
#     print(datetime.datetime.now())
#
# pc_timer = PeriodicCallback(callback=test_timer, callback_time=5000)
# pc_timer.start()

if __name__ == "__main__":

    port = int(sys.argv[1])
    application.listen(port=port)
    tornado.ioloop.IOLoop.instance().start()